package com.iretailer.report.web.report;

import com.iretailer.report.base.BaseAction;
import com.iretailer.report.dto.*;
import com.iretailer.report.dto.report.ReGroupBy;
import com.iretailer.report.dto.report.ReReport;
import com.iretailer.report.influxdb.dao.InfluxdbDao;
import com.iretailer.report.model.*;
import com.iretailer.report.service.report.ReportAsync;
import com.iretailer.report.util.BeanUtils;
import com.iretailer.report.util.CommonUtil;
import com.iretailer.report.util.RMap;
import com.iretailer.report.util.TimeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * @Auther: xjw
 * @Date: 2019/1/28 13:56
 * @Description:
 */
@RestController
public class ReportController extends BaseAction {

    private static final Logger log = LoggerFactory.getLogger(ReportController.class);

    @Autowired
    private ReportAsync reportAsync;
    @Autowired
    InfluxdbDao influxdbDao;

    @RequestMapping("/report/GlobalReport.action")
    public Map<String, Object> globalReport(@RequestBody Map<String, ReReport> reReport) {
        try {
            Map<String, Object> result = new HashMap<>();
            for (Map.Entry<String, ReReport> report : reReport.entrySet()) {
                result.put(report.getKey(), returnResult(report.getValue()));
            }
            return RMap.ok(result);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return RMap.error("error");
        }
    }

    /**
     * @Author xjw
     * @Date 2019/2/14 14:56
     * @Description :  得到结果
     **/
    private List<List<GlobalReportModel>> returnResult(ReReport reReport) {
        Map<String, String> dataFieldsMap = reReport.getDataFieldsMap();
        List<List<GlobalReportModel>> result = new ArrayList<>();
        //如果传入的 zoneTypeId 为 null 默认为 主出入口
        if (reReport.getLocations().getZoneType() == null) {
            Categories entrance = categoriesService.selectCategoryByKeyName("Entrance");
            List<String> categoriesIds = new ArrayList<>();
            categoriesIds.add(entrance.getId());
            if (dataFieldsMap.get("Passby") != null || dataFieldsMap.get("EnteringRate") != null) {
                Categories mallShop = categoriesService.selectCategoryByKeyName("MallShop");
                categoriesIds.add(mallShop.getId());
            }
            reReport.getLocations().setZoneType(categoriesIds);
        } else {
            List<Categories> categories = categoriesService.selectCategoryByKeyNames(reReport.getLocations().getZoneType());
            List<String> zoneTypes = categories.stream().map(Categories::getId).collect(Collectors.toList());
            reReport.getLocations().setZoneType(zoneTypes);
        }

        List<PhysicalZoneMall> physicalZones = new ArrayList<>();
        List<DeviceMall> deviceMalls = new ArrayList<>();
        List<Malls> malls = new ArrayList<>();
        //获取 案场以及 要查询的 区域
        boolean status = false;
        try {
            status = reportAsync.getMallAndPhysicalZone(reReport, physicalZones, deviceMalls, malls);
        } catch (Exception e) {
            return Collections.emptyList();
        }
        List<String> mallIds = null;
        if (status) {
            // 必须在 getMallAndPhysicalZone 之后
            mallIds = malls.stream().map(Malls::getId).collect(Collectors.toList());
        }
        String findStartTime = TimeUtil.getStartTime(reReport.getStartDate());
        String findEndTime = TimeUtil.getEndTime(reReport.getEndDate());


        long startQuery = System.currentTimeMillis();

        List<ReGroupBy> sortReGroupBy = reReport.getGroupBy().stream().sorted(Comparator.comparing(ReGroupBy::getFlow_periodIndex)).collect(Collectors.toList());


        //flow
        boolean flowStatus = false;
        List<CameraFlowModel> flows = null;
        if (dataFieldsMap.get("Enter") != null || dataFieldsMap.get("Exit") != null) {
            flowStatus = true;
            flows = influxdbFlowService.getPhysicalZones_PeriodFlow(physicalZones, findStartTime, findEndTime, sortReGroupBy.get(0).getFlow_period());
        }
        Map<String, Weathers> weatherMap = null;
        if (dataFieldsMap.get("WeatherName") != null) {
            weatherMap = reportAsync.weather(malls, reReport.getStartDate(), reReport.getEndDate());
        }
        List<PassByModel> passBys = null;
        if (dataFieldsMap.get("Passby") != null || dataFieldsMap.get("EnteringRate") != null) {
            passBys = influxdbPassByService.getPhysicalZones_PeriodPassByFlow(physicalZones, findStartTime, findEndTime, sortReGroupBy.get(0).getFlow_period());
        }

        List<DwellTimeModel> dwellTimeModels = null;
        if (dataFieldsMap.get("StayCount") != null) {
            dwellTimeModels = influxdbDwellService.getPhysicalZones_PeriodPassByFlow(physicalZones, findStartTime, findEndTime, sortReGroupBy.get(0).getFlow_period());
        }
        List<GenderAgeImage> genders = null;
        if (dataFieldsMap.get("Gender") != null) {
            genders = faceService.selectGroupByGender(mallIds, reReport.getStartDate(), reReport.getEndDate());
        }
        List<GenderAgeImage> ageTypes = null;
        Map<String, Categories> ageTypeMap = null;
        if (dataFieldsMap.get("AgeType") != null) {
            ageTypeMap = categoriesService.getAgeTypeMap();
            ageTypes = faceService.selectGroupByAgeType(mallIds, reReport.getStartDate(), reReport.getEndDate());
        }

        List<EasAlarmModel> easAlarmModels = null;
        if (dataFieldsMap.get("EasAlarm") != null) {
//            easAlarmModels = influxdbEasAlarmService.getDeviceId_PeriodEasAlarm(deviceMalls, findStartTime, findEndTime, sortReGroupBy.get(0).getFlow_period())
            List<AlarmEasModel> alarmEasModels = influxdbDao.selectSummaryAlarmEas("Asia/Shanghai", deviceMalls.stream().map(Device::getId).map(Long::intValue).collect(Collectors.toList()),
                    findStartTime, findEndTime, sortReGroupBy.get(0).getFlow_period());
            easAlarmModels = alarmEasModels.stream().map(m -> {
                EasAlarmModel model = new EasAlarmModel();
                model.setDateTime(m.getTime().format(TimeUtil.DEFAULT_DATE_TIME_FORMATTER));
                model.setAlarmTimes(m.getAlertTimes());
                model.setDeviceId((long) m.getDeviceId());
                model.setZonedDateTime(m.getTime());
                DeviceMall dm = CommonUtil.find(deviceMalls, (Predicate<DeviceMall>) deviceMall -> m.getDeviceId().equals(deviceMall.getId().intValue()));
                if (dm != null) {
                    model.setMallId(dm.getMallId());
                    model.setMallName(dm.getMallName());
                }
                return model;
            }).collect(Collectors.toList());
        }

        List<Sales> salesList = null;
        if (dataFieldsMap.get("Sales") != null) {
            salesList = salesService.queryMallsSales(mallIds, reReport.getStartDate(), reReport.getEndDate());
        }

        long endQuery = System.currentTimeMillis();
        log.error("------- 查询时间 {}", (endQuery - startQuery));

        for (ReGroupBy groupMap : reReport.getGroupBy()) {
            //得到数据的时间组
            List<String> timesArray = TimeUtil.getPeriodStr(reReport.getStartDate(), reReport.getEndDate(), true, reReport.getStartTime(), reReport.getEndTime(), groupMap.getPeriod(), groupMap.getTimeFormatter_());

            //得到返回的结果
            List<GlobalReportModel> globalReportModels = getGlobalReportModels(timesArray, malls, physicalZones, deviceMalls, reReport, groupMap);


            //设置 flow, WeatherName
            reportAsync.setFlow(globalReportModels, flows, groupMap);

            //设置 passBy
            reportAsync.setPassByFlow(globalReportModels, passBys, groupMap, flowStatus);

            //设置 停留时间
            reportAsync.setDwellTimeModel(globalReportModels, dwellTimeModels, groupMap);

            //设置天气
            reportAsync.setWeather(globalReportModels, weatherMap);

            // 设置 性别比例，年龄比例
            reportAsync.setFace(globalReportModels, genders, ageTypes, ageTypeMap);

            // 设置 Eas Alarm 数据

            reportAsync.setEasAlarm(globalReportModels, easAlarmModels, groupMap);

            // 设置销售数据
            reportAsync.setSales(globalReportModels, salesList, groupMap);

            //设置 stay 并对 GlobalReportModel 按时间排序
            switch (groupMap.getPeriod()) {
                case "All":
                case "Year":
                case "Month":
                case "Week":
                case "Weekday":
                case "Hourly":
                case "WeekPeak":
                case "MonthPeak":
                case "YearPeak":
                    globalReportModels.forEach(it -> {
                        it.setStay(it.getEnter() - it.getExit());
                    });
                    switch (groupMap.getTimeFormatter()) {
                        case "All":
                            result.add(globalReportModels);
                            break;
                        case "Weekday":
                            result.add(globalReportModels.stream().sorted(Comparator.comparing(GlobalReportModel::getTimeLabel, (x, y) -> {
                                return TimeUtil.getWeekDaysIndex(x) - TimeUtil.getWeekDaysIndex(y);
                            })).collect(Collectors.toList()));
                            break;
                        default:
                            result.add(globalReportModels.stream().sorted(Comparator.comparing(GlobalReportModel::getDateTime)).collect(Collectors.toList()));
                            break;
                    }
                    break;
                // 5m,15m,30m,60m,1d
                default:
                    LocalDateTime nowTime = reReport.getNowTime();
                    Map<String, List<GlobalReportModel>> domainLabeGroupby = globalReportModels.stream().collect(Collectors.groupingBy(GlobalReportModel::getDomainLabel));
                    List<GlobalReportModel> groupResult = new ArrayList<>();
                    domainLabeGroupby.forEach((key, value) -> {
                        value.stream().collect(Collectors.groupingBy(GlobalReportModel::getDate)).forEach((k, v) -> {
                            List<GlobalReportModel> sorted = v.stream().sorted(Comparator.comparing(GlobalReportModel::getDateTime)).collect(Collectors.toList());
                            groupResult.addAll(flowStayCompute(sorted, nowTime));
                        });
                    });
                    result.add(groupResult.stream().sorted(Comparator.comparing(GlobalReportModel::getDateTime).thenComparing(GlobalReportModel::getRanked)).collect(Collectors.toList()));
                    break;


            }
        }
        long computeTime = System.currentTimeMillis();
        log.error("------- 计算时间 {}", (computeTime - endQuery));
        return result;
    }


    public List<GlobalReportModel> getGlobalReportModels(List<String> timesArray, List<Malls> malls, List<PhysicalZoneMall> physicalZones, List<DeviceMall> deviceMalls, ReReport reReport, ReGroupBy groupMap) {
        List<GlobalReportModel> result = new ArrayList<>();
        Stream<GlobalReportModel> join_model = null;
        String mallId = malls.size() > 0 ? malls.get(0).getId() : null;
        switch (groupMap.getDomain()) {
            case "All":
                int clerk_all = 0;
                int operationAcreage_all = 0;
                for (Malls m : malls) {
                    clerk_all += m.getClerkNumber();
                    operationAcreage_all += m.getOperationAcreage();
                }
                //  mallId 用去匹配天气
                join_model = Stream.of(GlobalReportModel.getDomainLabelInit("All", mallId, clerk_all, operationAcreage_all, 0));
                break;
            case "Tag":
                List<ReTagType> tagTypes = reReport.getLocations().getTagTypes();
                Map<String, Malls> mallsMap = malls.stream().collect(Collectors.toMap(Malls::getId, a -> a));
                List<GlobalReportModel> models = new ArrayList<>();
                for (ReTagType tt : tagTypes) {
                    int clerk_tag = 0;
                    int operationAcreage_tag = 0;
                    List<String> mIds = tt.getMallIds();
                    for (String mId : mIds) {
                        Malls m = mallsMap.get(mId);
                        if (m != null) {
                            clerk_tag += m.getClerkNumber();
                            operationAcreage_tag += m.getOperationAcreage();
                        }
                    }
                    models.add(GlobalReportModel.getTagTypeInit(tt.getName(), mIds.get(0), clerk_tag, operationAcreage_tag, tt.getRanked(), tt.getId(), mIds));
                }
                join_model = models.stream();
                break;
            case "Mall":
                join_model = malls.stream().map(it ->
                        GlobalReportModel.getMallIdInit(it.getName(), it.getId(), it.getClerkNumber(), it.getOperationAcreage(), it.getRanked()));
                break;
            case "Zone":
                join_model = physicalZones.stream().map(it ->
                        GlobalReportModel.getPhyIdInit(it.getName(), it.getMallId(), String.valueOf(it.getId()), it.getRanked()));
                break;
            case "Week":
                join_model = TimeUtil.weekDays.stream().map(it -> GlobalReportModel.getDomainLabelInit(it, mallId, 0, 0, 0));
                break;
            case "Device":
                join_model = deviceMalls.stream().map(it -> GlobalReportModel.getDeviceInit(it.getDeviceName(), it.getMallId(), String.valueOf(it.getId()), it.getRanked()));
                break;
        }
        join_model.forEach(g -> {
            timesArray.forEach(time -> {
//                GlobalReportModel n = new GlobalReportModel(g.getDomainLabel(), g.getMallId(),
//                        g.getPhyId(), time, groupMap.getTimeFormatter_());
                g.setTimeLabel(time);
                g.setTimeFormatter(groupMap.getTimeFormatter_());
                GlobalReportModel n = new GlobalReportModel();
                BeanUtils.copyPropertiesASM(g, n);
                result.add(n);
            });
        });
        return result;
    }

    /**
     * 计算停留，停留需要累计
     *
     * @param sorted
     * @return
     */
    private List<GlobalReportModel> flowStayCompute(List<GlobalReportModel> sorted, LocalDateTime nowTime) {
        for (int i = 0, len = sorted.size(); i < len; i++) {
            GlobalReportModel thisG = sorted.get(i);
            boolean status = true;
            if (nowTime != null) {
                LocalDateTime gTime = TimeUtil.parse_LocalDateTime(thisG.getDateTime());
                status = !gTime.isAfter(nowTime);
            }
            if (status) {
                thisG.setStay(thisG.getEnter() - thisG.getExit());
                if (i >= 1) {
                    GlobalReportModel prevG = sorted.get(i - 1);
                    thisG.setStay(thisG.getStay() + prevG.getStay());
                }
            } else {
                thisG.setStay(0);
            }
        }
        return sorted;
    }

    private Malls getTotalClerkAndAcreage(List<Malls> malls) {
        int clerk = 0;
        int acreage = 0;
        if (malls != null && malls.size() > 0) {
            for (Malls m : malls) {
                clerk += m.getClerkNumber();
                acreage += m.getOperationAcreage();
            }
        }
        Malls result = new Malls();
        result.setClerkNumber(clerk);
        result.setOperationAcreage(acreage);
        return result;
    }
}
